package xyz.ck.commons.interval;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import xyz.ck.commons.util.StringUtil;

public class IntervalGroup<T> implements Interval<T> {

	/**
	 * 
	 */
	private static final long serialVersionUID = -3843072807181777124L;

	@SuppressWarnings("rawtypes")
	public static class UnitComparator implements Comparator<IntervalUnit> {
		@SuppressWarnings("unchecked")
		@Override
		public int compare(IntervalUnit o1, IntervalUnit o2) {
			return o1.compare(o1.min(), o2.min());
		}
	}

	@SuppressWarnings("rawtypes")
	public static Comparator<IntervalUnit> comparator = new UnitComparator();

	private List<IntervalUnit<T>> units = null;

	public List<IntervalUnit<T>> getUnits() {
		return units;
	}

	public void setUnits(List<IntervalUnit<T>> items) {
		this.units = items;
	}

	public IntervalGroup() {
		super();
	}

	@SafeVarargs
	public static <T> Interval<T> unions(Interval<T>... intervals) {
		List<IntervalUnit<T>> results = new ArrayList<IntervalUnit<T>>();
		for (int i = 0; i < intervals.length; i++) {
			if (intervals[i] instanceof IntervalGroup) {
				IntervalGroup<T> _group = (IntervalGroup<T>) intervals[i];
				for (int j = 0; j < _group.units.size(); j++) {
					results.add(_group.units.get(j));
				}
			} else if (intervals[i] instanceof IntervalUnit) {
				results.add((IntervalUnit<T>) intervals[i]);
			}
		}
		List<IntervalUnit<T>> items = unionUnits(results);

		if (items.size() == 1) {
			return items.get(0);
		} else {
			IntervalGroup<T> group = instance(items);
			return group;
		}
	}

	public static <T> Interval<T> unions(Collection<Interval<T>> intervals) {
		List<IntervalUnit<T>> results = new ArrayList<IntervalUnit<T>>();
		for (Interval<T> interval : intervals) {
			if (interval instanceof IntervalGroup) {
				IntervalGroup<T> _group = (IntervalGroup<T>) interval;
				for (int j = 0; j < _group.units.size(); j++) {
					results.add(_group.units.get(j));
				}
			} else if (interval instanceof IntervalUnit) {
				results.add((IntervalUnit<T>) interval);
			}
		}
		List<IntervalUnit<T>> items = unionUnits(results);

		if (items.size() == 1) {
			return items.get(0);
		} else {
			IntervalGroup<T> group = instance(items);
			return group;
		}
	}

	public static <E> IntervalGroup<E> instance(
			List<IntervalUnit<E>> itervalItems) {
		IntervalGroup<E> group = new IntervalGroup<E>();
		group.setUnits(itervalItems);
		return group;
	}

	@SafeVarargs
	public static <E> IntervalGroup<E> instance(IntervalUnit<E>... itervalItems) {
		IntervalGroup<E> group = new IntervalGroup<E>();
		List<IntervalUnit<E>> items = new ArrayList<IntervalUnit<E>>();

		for (IntervalUnit<E> unit : itervalItems) {
			items.add(unit);
		}
		group.setUnits(items);
		return group;
	}

	public static <E> List<IntervalUnit<E>> unionUnits(
			List<IntervalUnit<E>> itervalItems) {
		if (itervalItems == null || itervalItems.isEmpty()) {
			return null;
		}
		if (itervalItems.size() == 1) {
			return itervalItems;
		}
		Collections.sort(itervalItems, comparator);
		List<IntervalUnit<E>> result = new ArrayList<IntervalUnit<E>>();
		int i = 0;
		IntervalUnit<E> current = itervalItems.get(0);
		while (i + 1 < itervalItems.size()) {
			IntervalUnit<E> next = itervalItems.get(i + 1);
			Interval<E> joined = current.union(next);
			if (joined.isGroup()) {
				result.add(current);
				current = next;
				if ((i + 1) == (itervalItems.size() - 1)) {
					result.add(next);
				}
			} else {
				if ((i + 1) == (itervalItems.size() - 1)) {
					result.add((IntervalUnit<E>) joined);
				} else {
					current = (IntervalUnit<E>) joined;
				}
			}
			i++;
		}
		return result;
	}

	@Override
	public boolean isContains(Interval<T> interval) {
		if (this.isEmpty()) {
			return false;
		}
		if (interval.isEmpty()) {
			return false;
		}
		if (interval instanceof IntervalUnit) {
			for (IntervalUnit<T> unit : this.units) {
				if (unit.isContains(unit)) {
					return true;
				}
			}
		} else if (interval instanceof IntervalGroup) {
			IntervalGroup<T> target = (IntervalGroup<T>) interval;
			for (IntervalUnit<T> targetItem : target.units) {
				if (this.isContains(targetItem)) {
					return true;
				}
			}
		} else {
			throw new ClassCastException(
					"Class cust Exception: not supported Interval Type!");
		}
		return false;
	}

	@Override
	public boolean isIn(T t) {
		if (isEmpty()) {
			return false;
		}
		for (IntervalUnit<T> unit : this.units) {
			if (unit.isIn(t)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean isIntersect(Interval<T> interval) {
		if (isEmpty()) {
			return false;
		}

		if (interval.isEmpty()) {
			return false;
		}
		if (interval instanceof IntervalUnit) {
			IntervalUnit<T> target = (IntervalUnit<T>) interval;
			for (IntervalUnit<T> unit : this.units) {
				if (unit.isIntersect(target)) {
					return true;
				}
			}
		} else if (interval instanceof IntervalGroup) {
			IntervalGroup<T> target = (IntervalGroup<T>) interval;
			for (IntervalUnit<T> targetUnit : target.units) {
				if (this.isIntersect(targetUnit)) {
					return true;
				}
			}
		} else {
			throw new ClassCastException(
					"Class cust Exception: not supported Interval Type!");
		}

		return false;
	}

	@Override
	public Interval<T> union(Interval<T> interval) {
		if (this.isEmpty()) {
			return interval;
		}
		if (interval.isEmpty()) {
			return this;
		}

		return unions(this, interval);

	}

	@Override
	public Interval<T> minus(Interval<T> interval) {
		List<IntervalUnit<T>> results = new ArrayList<IntervalUnit<T>>();

		for (IntervalUnit<T> unit : this.units) {
			Interval<T> minus = unit.minus(interval);
			if (minus == null) {

			} else if (minus.isUnit()) {
				IntervalUnit<T> intervalUnit = (IntervalUnit<T>) minus;
				results.add(intervalUnit);
			} else if (minus.isGroup()) {
				IntervalGroup<T> group = (IntervalGroup<T>) minus;
				results.addAll(group.units);
			}

		}

		if (results.isEmpty()) {
			return null;
		}

		if (results.size() == 1) {
			return results.get(0);
		}
		return instance(results);
		// throw new IntervalException("unknow source!");
	}

	@Override
	public Interval<T> intersect(Interval<T> interval) {
		if (interval == null || interval.isEmpty()) {
			return null;
		}
		if (interval.isUnit()) {
			List<IntervalUnit<T>> results = new ArrayList<IntervalUnit<T>>();
			for (Interval<T> unit : this.units) {
				Interval<T> intersected = unit.intersect(interval);
				if (intersected != null) {
					results.add((IntervalUnit<T>) intersected);
				}
			}
			if (results.isEmpty()) {
				return null;
			}
			return instance(results);
		} else if (interval.isGroup()) {
			List<IntervalUnit<T>> results = new ArrayList<IntervalUnit<T>>();
			IntervalGroup<T> targetGroup = (IntervalGroup<T>) interval;
			for (IntervalUnit<T> unit : this.units) {

				for (IntervalUnit<T> targetUnit : targetGroup.units) {
					Interval<T> intersected = unit.intersect(targetUnit);
					if (intersected != null) {
						results.add((IntervalUnit<T>) intersected);
					}
				}

			}

			if (results.isEmpty()) {
				return null;
			}
			return instance(results);

		}
		throw new ClassCastException("unknown Interval Type:" + interval);
	}

	@Override
	public String getStringValue() {
		return StringUtil.joins(",", this.units);
	}

	@Override
	public boolean isEmpty() {
		if (this.units == null || this.units.isEmpty()) {
			return true;
		}
		for (IntervalUnit<T> unit : this.units) {
			if (!unit.isEmpty()) {
				return false;
			}
		}
		return true;
	}

	@Override
	public String toString() {

		return getStringValue();
	}

	@Override
	public boolean isGroup() {

		return true;
	}

	@Override
	public boolean isUnit() {

		return false;
	}

	@Override
	public boolean equals(Object obj) {

		if (obj == null) {
			return false;
		}
		if (this == obj) {
			return true;
		}
		if (obj instanceof IntervalGroup) {
			@SuppressWarnings("unchecked")
			IntervalGroup<T> group = (IntervalGroup<T>) obj;

			if (this.units == group.units) {
				return true;
			}

			for (int i = 0; i < this.units.size(); i++) {
				if (!this.units.get(i).equals(group.units.get(i))) {
					return false;
				}
			}
			return true;
		}
		return false;
	}

	@Override
	public T min() {
		if (this.isEmpty()) {
			return null;
		} else {
			return this.getUnits().get(0).min();
		}
	}

	@Override
	public T max() {
		if (this.isEmpty()) {
			return null;
		} else {
			List<IntervalUnit<T>> units = this.getUnits();
			IntervalUnit<T> unit = units.get(units.size() - 1);
			return unit.max();
		}
	}

	@Override
	public boolean isContinuous() {
		return this.getUnits().get(0).isContinuous();
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}

}
